package codediff;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jgit.diff.DiffEntry;
import util.Md5Util;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static util.Md5Util.getMd5Str;

public class ASTGenerator {
    private String javaText;
    private CompilationUnit compilationUnit;
    private TypeDeclaration javaClazz;

    public ASTGenerator(String javaText) {
        this.javaText = javaText;
        this.initCompilationUnit();
    }

    /**
     * 修改入参也会导致方法不一样，注意敏感度 method.getBody()
     */
    public static boolean isMethodTheSame(final MethodDeclaration method1, final MethodDeclaration method2) {
        return Md5Util.isSameMethod(method1.toString(), method2.toString());
    }

    /**
     * 获取AST编译单元,首次加载很慢
     */
    private void initCompilationUnit() {
        final ASTParser astParser = ASTParser.newParser(11);
        final Map<String, String> options = JavaCore.getOptions();
        JavaCore.setComplianceOptions(JavaCore.VERSION_11, options);
        astParser.setCompilerOptions(options);
        astParser.setKind(ASTParser.K_COMPILATION_UNIT);
        astParser.setResolveBindings(true);
        astParser.setBindingsRecovery(true);
        astParser.setStatementsRecovery(true);
        astParser.setSource(javaText.toCharArray());
        compilationUnit = (CompilationUnit) astParser.createAST(null);
        Assert.isTrue(Objects.nonNull(compilationUnit), "编译错误");
        javaClazz = getJavaClass();
    }

    /**
     * 获取java类包名     *     * @return
     */
    public String getPackageName() {
        PackageDeclaration packageDeclaration = compilationUnit.getPackage();
        if (packageDeclaration == null) {
            return "";
        }        /**         *  TODO: 2021/1/8 . 和 /         */
        return packageDeclaration.getName().toString().replace('.', '/');
    }

    /**
     * 获取普通类单元     *     * @return
     */
    public TypeDeclaration getJavaClass() {
        return (TypeDeclaration) compilationUnit.types().stream()
                .filter(type -> type instanceof TypeDeclaration)
                .findFirst()
                .orElse(null);
    }

    /**
     * 获取java类中所有方法     *     * @return 类中所有方法
     */
    public MethodDeclaration[] getMethods() {
        return Optional.ofNullable(javaClazz).map(TypeDeclaration::getMethods).orElse(new MethodDeclaration[]{});
    }

    public IncrClassInfo generateAddClassInfo(List<String> committers) {
        return Optional.ofNullable(javaClazz)
                .filter(it -> !it.isInterface())
                .map(it -> IncrClassInfo.builder()
                        .className(it.getName().toString())
                        .packageName(getPackageName())
                        .delLines(Collections.emptyList())
                        .addLines(Collections.emptyList())
                        .incrMethodInfos(Arrays.stream(getMethods()).map(this::mapToIncrMethodInfo).collect(Collectors.toList()))
                        .changeType(DiffEntry.ChangeType.ADD.name())
                        .committers(committers)
                        .build())
                .orElse(null);
    }

    public IncrClassInfo generateModifyClassInfo(List<IncrMethodInfo> incrMethodInfos, List<Integer> delLines, List<Integer> addLines, List<String> committers) {
        return Optional.ofNullable(javaClazz).filter(it -> !it.isInterface())
                .map(it -> IncrClassInfo.builder()
                        .className(it.getName().toString())
                        .packageName(getPackageName())
                        .incrMethodInfos(incrMethodInfos)
                        .delLines(delLines)
                        .addLines(addLines)
                        .changeType(DiffEntry.ChangeType.MODIFY.name())
                        .committers(committers).build())
                .orElse(null);
    }

    public IncrMethodInfo mapToIncrMethodInfo(MethodDeclaration methodDeclaration) {
        int start = compilationUnit.getLineNumber(methodDeclaration.getStartPosition());
        int end = compilationUnit.getLineNumber(methodDeclaration.getStartPosition() + methodDeclaration.getLength());
        return IncrMethodInfo.builder()
                .md5(getMd5Str(methodDeclaration.toString()))
                .methodName(methodDeclaration.getName().toString())
                .parameters(methodDeclaration.parameters().toString())
                .includeLines(IntStream.range(start, end + 1).boxed().collect(Collectors.toList()))
                .isConstructor(methodDeclaration.isConstructor())
                .build();
    }

}